//
// Created by Mysher on 2021/5/7.
//

#include "Decoder.h"

#include "android_log.h"

extern "C"
{
bool CDecoder::init(DecoderType type, IDecoderGotPictureListener *listener) {
    first_time = 1;
    m_listener = listener;

    AVCodecID codec_id = AV_CODEC_ID_H264;
    if(type == DecoderType_H265) {
        codec_id = AV_CODEC_ID_HEVC;
    }

    avcodec_register_all();

    pCodec = avcodec_find_decoder(codec_id);
    if (!pCodec) {
        printf("Codec not found\n");
        return false;
    }
    pCodecCtx = avcodec_alloc_context3(pCodec);
    if (!pCodecCtx){
        printf("Could not allocate video codec context\n");
        return false;
    }

    pCodecParserCtx=av_parser_init(codec_id);
    if (!pCodecParserCtx){
        printf("Could not allocate video parser context\n");
        return false;
    }

    //if(pCodec->capabilities&CODEC_CAP_TRUNCATED)
    //    pCodecCtx->flags|= CODEC_FLAG_TRUNCATED;

    if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
        printf("Could not open codec\n");
        return false;
    }

    pFrame = av_frame_alloc();

    pPacket = av_packet_alloc();
    av_init_packet(pPacket);

    return true;
}

bool CDecoder::init(DecoderType type) {
    first_time = 1;

    AVCodecID codec_id = AV_CODEC_ID_H264;
    if(type == DecoderType_H265) {
        codec_id = AV_CODEC_ID_HEVC;
    }

    avcodec_register_all();

    pCodec = avcodec_find_decoder(codec_id);
    if (!pCodec) {
        printf("Codec not found\n");
        return false;
    }
    pCodecCtx = avcodec_alloc_context3(pCodec);
    if (!pCodecCtx){
        printf("Could not allocate video codec context\n");
        return false;
    }

    pCodecParserCtx=av_parser_init(codec_id);
    if (!pCodecParserCtx){
        printf("Could not allocate video parser context\n");
        return false;
    }

    //if(pCodec->capabilities&CODEC_CAP_TRUNCATED)
    //    pCodecCtx->flags|= CODEC_FLAG_TRUNCATED;

    if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
        printf("Could not open codec\n");
        return false;
    }

    pFrame = av_frame_alloc();

    pPacket = av_packet_alloc();
    av_init_packet(pPacket);

    return true;
}

bool CDecoder::decode(unsigned char *in_buffer, int in_buffer_size, int &effect_len) {
    LOGD("decode begin");
    int ret, got_picture;

    effect_len = av_parser_parse2(
            pCodecParserCtx, pCodecCtx,
            &pPacket->data, &pPacket->size,
            in_buffer , in_buffer_size ,
            AV_NOPTS_VALUE, AV_NOPTS_VALUE, AV_NOPTS_VALUE);
    LOGD("av_parser_parse2 = %d", effect_len);
    if(pPacket->size==0)
        return false;

    //Some Info from AVCodecParserContext
    printf("[Packet]Size:%6d\t",pPacket->size);
    LOGD("[Packet]Size:%6d\t",pPacket->size);
    switch(pCodecParserCtx->pict_type){
        case AV_PICTURE_TYPE_I:
            printf("Type:I\t");
            LOGD("Type:I\t");
            break;
        case AV_PICTURE_TYPE_P:
            printf("Type:P\t");
            LOGD("Type:P\t");
            break;
        case AV_PICTURE_TYPE_B:
            printf("Type:B\t");
            LOGD("Type:B\t");
            break;
        default:
            printf("Type:Other\t");
            break;
    }
    printf("Number:%4d\n",pCodecParserCtx->output_picture_number);
    LOGD("Number:%4d\n",pCodecParserCtx->output_picture_number);

    ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, pPacket);
    if (ret < 0) {
        printf("Decode Error.\n");
        return false;
    }
    if (got_picture) {
        if(first_time){
            printf("\nCodec Full Name:%s\n",pCodecCtx->codec->long_name);
            LOGD("\nCodec Full Name:%s\n",pCodecCtx->codec->long_name);
            printf("width:%d\nheight:%d\n\n",pCodecCtx->width,pCodecCtx->height);
            LOGD("width:%d\nheight:%d\n\n",pCodecCtx->width,pCodecCtx->height);
            first_time=0;
        }

        if(m_listener != NULL) {
            int out_buffer_size = pFrame->width*pFrame->height*3/2;
            unsigned char *out_buffer = new unsigned char[out_buffer_size];
            int index = 0;
            //Y, U, V
            for(int i=0;i<pFrame->height;i++){
                //fwrite(pFrame->data[0]+pFrame->linesize[0]*i,1,pFrame->width,fp_out);
                memcpy(out_buffer + index, pFrame->data[0]+pFrame->linesize[0]*i, pFrame->width);
                index += pFrame->width;
            }
            for(int i=0;i<pFrame->height/2;i++){
                //fwrite(pFrame->data[1]+pFrame->linesize[1]*i,1,pFrame->width/2,fp_out);
                memcpy(out_buffer + index, pFrame->data[1]+pFrame->linesize[1]*i, pFrame->width/2);
                index += pFrame->width/2;
            }
            for(int i=0;i<pFrame->height/2;i++){
                //fwrite(pFrame->data[2]+pFrame->linesize[2]*i,1,pFrame->width/2,fp_out);
                memcpy(out_buffer + index, pFrame->data[2]+pFrame->linesize[2]*i, pFrame->width/2);
                index += pFrame->width/2;
            }

            m_listener->onGotPicture(out_buffer, out_buffer_size);

            delete out_buffer;
        }


        printf("Succeed to decode 1 frame!\n");
        LOGD("Succeed to decode 1 frame!\n");
    }
    LOGD("decode end");
    return true;
}

bool CDecoder::decode(unsigned char *in_buffer, int in_buffer_size) {
    LOGD("decode begin");
    m_decodeResult.m_nGotPicture = 0;
    int ret = -1;

    m_decodeResult.m_nLen = av_parser_parse2(
            pCodecParserCtx, pCodecCtx,
            &pPacket->data, &pPacket->size,
            in_buffer , in_buffer_size ,
            AV_NOPTS_VALUE, AV_NOPTS_VALUE, AV_NOPTS_VALUE);
    LOGD("av_parser_parse2 = %d", m_decodeResult.m_nLen);
    if(pPacket->size==0)
        return false;

    //Some Info from AVCodecParserContext
    printf("[Packet]Size:%6d\t",pPacket->size);
    LOGD("[Packet]Size:%6d\t",pPacket->size);
    switch(pCodecParserCtx->pict_type){
        case AV_PICTURE_TYPE_I:
            printf("Type:I\t");
            LOGD("Type:I\t");
            m_decodeResult.m_nFrameType = 1;
            break;
        case AV_PICTURE_TYPE_P:
            printf("Type:P\t");
            LOGD("Type:P\t");
            m_decodeResult.m_nFrameType = 2;
            break;
        case AV_PICTURE_TYPE_B:
            printf("Type:B\t");
            LOGD("Type:B\t");
            m_decodeResult.m_nFrameType = 3;
            break;
        default:
            printf("Type:Other\t");
            m_decodeResult.m_nFrameType = 0;
            break;
    }
    printf("Number:%4d\n",pCodecParserCtx->output_picture_number);
    LOGD("Number:%4d\n",pCodecParserCtx->output_picture_number);


    ret = avcodec_decode_video2(pCodecCtx, pFrame, &m_decodeResult.m_nGotPicture, pPacket);
    if (ret < 0) {
        printf("Decode Error.\n");
        return false;
    }

    if (m_decodeResult.m_nGotPicture) {
        if(first_time){
            printf("\nCodec Full Name:%s\n",pCodecCtx->codec->long_name);
            LOGD("\nCodec Full Name:%s\n",pCodecCtx->codec->long_name);
            printf("width:%d\nheight:%d\n\n",pCodecCtx->width,pCodecCtx->height);
            LOGD("width:%d\nheight:%d\n\n",pCodecCtx->width,pCodecCtx->height);
            first_time=0;
        }

        m_decodeResult.setWH(pFrame->width, pFrame->height);

        int index = 0;
        //Y, U, V
        for(int i=0;i<pFrame->height;i++){
            //fwrite(pFrame->data[0]+pFrame->linesize[0]*i,1,pFrame->width,fp_out);
            memcpy(m_decodeResult.m_ptOutBuffer + index, pFrame->data[0]+pFrame->linesize[0]*i, pFrame->width);
            index += pFrame->width;
        }
        for(int i=0;i<pFrame->height/2;i++){
            //fwrite(pFrame->data[1]+pFrame->linesize[1]*i,1,pFrame->width/2,fp_out);
            memcpy(m_decodeResult.m_ptOutBuffer + index, pFrame->data[1]+pFrame->linesize[1]*i, pFrame->width/2);
            index += pFrame->width/2;
        }
        for(int i=0;i<pFrame->height/2;i++){
            //fwrite(pFrame->data[2]+pFrame->linesize[2]*i,1,pFrame->width/2,fp_out);
            memcpy(m_decodeResult.m_ptOutBuffer + index, pFrame->data[2]+pFrame->linesize[2]*i, pFrame->width/2);
            index += pFrame->width/2;
        }

        printf("Succeed to decode 1 frame!\n");
        LOGD("Succeed to decode 1 frame!\n");
    }
    LOGD("decode end");
    return true;
}

#define SAFE_DELETE(x) if (nullptr != x) { delete x; x = nullptr; } // NOLINT

void CDecoder::destroy() {
    av_parser_close(pCodecParserCtx);

    av_frame_free(&pFrame);
    av_packet_free(&pPacket);
    avcodec_close(pCodecCtx);
    av_free(pCodecCtx);

    m_decodeResult.release();
}
}
